#define _GNU_SOURCE
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <assert.h>
#include <time.h>

#include "world.h"

#define DC_UP   0
#define DC_LEFT 1
#define DC_DOWN 2
#define DC_RIGHT 3


#define obj_limit_x (obj_max_x - obj_min_x + 1)
#define obj_limit_y (obj_max_y - obj_min_y + 1)

#if 1
#define obj_min_x -2
#define obj_max_x  2

#define obj_min_y -2
#define obj_max_y  1

static int ObjDef [obj_limit_y][obj_limit_x] = {
    {0, 0, 2, 0, 0},
    {1, 1, 1, 1, 1},
    {0, 0, 1, 0, 0},
    {0, 1, 1, 1, 0},
};

#else
#define obj_min_x -2
#define obj_max_x  2

#define obj_min_y -2
#define obj_max_y  1

int ObjDef [obj_limit_y][obj_limit_x] = {
    {0, 1, 0, 1, 0},
    {0, 1, 2, 1, 0},
    {0, 1, 3, 1, 0},
    {1, 0, 1, 0, 1},
    {1, 0, 0, 0, 1},
};
#endif

static int GetObjectDefAt(int x, int y)
{
    if (x < obj_min_x || y < obj_min_y 
            || x > obj_max_x || y > obj_max_y ) {
        return -1;
    }
    x = x - obj_min_x;
    y = obj_limit_y - (y - obj_min_y) - 1;
    return ObjDef[y][x];
}

typedef struct Object
{
    struct Object * next;
    int direction;
    int x;
    int y;
    void * data;
} Object;

typedef struct SlotInfo
{
    Object * obj;
    int hit;
} SlotInfo;

struct World
{
    int width;
    int height;

    Object * obj_list;

    struct SlotInfo slot[0];
};

static void ToObject(int * x, int * y, Object * obj)
{
    int world_x = *x;
    int world_y = *y;

    int obj_x = -1;
    int obj_y = -1;

    int obj_center_x = obj->x;
    int obj_center_y = obj->y;;

    switch(obj->direction) {
        case DC_UP:
            obj_x = world_x - obj_center_x;
            obj_y = world_y - obj_center_y;
            break;
        case DC_RIGHT:
            obj_x = obj_center_y - world_y;
            obj_y = world_x - obj_center_x;
            break;
        case DC_LEFT:
            obj_x = world_y - obj_center_y;
            obj_y = obj_center_x - world_x;
            break;
        case DC_DOWN:
            obj_x = obj_center_x - world_x;
            obj_y = obj_center_y - world_y;
            break;
        default:
            assert( 0 && "unknown directon");
    }

    *x = obj_x;
    *y = obj_y;
}

static void ToWorld(int * x, int * y, Object * obj)
{
    int world_x = -1;
    int world_y = -1;

    int obj_x = *x;
    int obj_y = *y;

    int obj_center_x = obj->x;
    int obj_center_y = obj->y;;

    switch(obj->direction) {
        case DC_UP:
            world_x = obj_center_x + obj_x;
            world_y = obj_center_y + obj_y;
            break;
        case DC_RIGHT:
            world_x = obj_center_x + obj_y;
            world_y = obj_center_y - obj_x;
            break;
        case DC_LEFT:
            world_x = obj_center_x - obj_y;
            world_y = obj_center_y + obj_x;
            break;
        case DC_DOWN:
            world_x = obj_center_x - obj_x;
            world_y = obj_center_y - obj_y;
            break;
        default:
            assert(0 && "unknown directon");
    }
    *x = world_x;
    *y = world_y;
}

static int IsValidePosition(World * w, int x, int y)
{
    if (w == 0 || x < 0 || x >= w->width ||
        y < 0 || y >= w->height) {
        return 0;
    }
    return 1;
}

static Object * GetObjectAt(World * w, int x, int y)
{
    if (!IsValidePosition(w, x, y) ) {
        return 0;
    }
    return w->slot[y * w->width + x].obj;
}


static int CanAddObject(World * w, int x, int y, int direction)
{
    Object obj;
    obj.x = x;
    obj.y = y;
    obj.direction = direction;
    for (y = obj_min_y; y <= obj_max_y; y++) {
        for(x = obj_min_x; x <= obj_max_x; x++) {
            int c = GetObjectDefAt(x, y);
            if (c > 0) {
                int world_x = x, world_y = y;
                ToWorld(&world_x, &world_y, &obj);
                if (!IsValidePosition(w, world_x, world_y)){
                    return 0;
                }

                if (GetObjectAt(w, world_x, world_y) != 0) {
                    return 0;
                }
            }
        }
    }
    return 1;
}

static void SetObject(World * w, int x, int y, Object * obj)
{
    w->slot[y * w->width + x].obj = obj;
}

static void AddObject(World * w, Object * obj)
{
    int x, y;
    for (y = obj_min_y; y <= obj_max_y; y++) {
        for(x = obj_min_x; x <= obj_max_x; x++) {
            int c = GetObjectDefAt(x, y);
            if (c > 0) {
                int world_x = x, world_y = y;
                ToWorld(&world_x, &world_y, obj);
                SetObject(w, world_x, world_y, obj);
            }
        }
    }
    obj->next = w->obj_list;
    w->obj_list = obj;
}

int CreateObject(World * w, int x, int y, int direction)
{
    if (w == 0 || x >= w->width || x > w->height
        || direction < 0 || direction >= 4) {
        return -1;
    }

    if (!CanAddObject(w, x, y, direction)) {
        return -1;
    }

    Object * obj = malloc(sizeof(Object));
    if (obj == 0) {
        return -1;
    }

    obj->x = x;
    obj->y = y;
    obj->direction = direction;
    obj->next = 0;
    AddObject(w, obj);
    return 0;
}

World * CreateWorld(int width, int height, int objs)
{
    if (width < 0 || height < 0) {
        return 0;
    }

    World * w = malloc(sizeof(World) + sizeof(SlotInfo) * width * height);
    if (w) {
        w->width = width;
        w->height = height;
        w->obj_list = 0;
        memset(w->slot, 0, sizeof(SlotInfo) * width * height);
        srand(time(0));

        while (objs > 0) {
            int x = rand() % 10;
            int y = rand() % 10;
            int d = rand() % 4;

            if (CreateObject(w, x, y, d) != 0) {
                continue;
            }
            objs--;
        }
    }
    return w;
}

void DestroyWorld(World * w)
{
    if (w) {
        while (w->obj_list) {
            Object * o = w->obj_list;
            w->obj_list = o->next;
            free(o);
        }
        free(w);
    }
}

static int IsHit(World * w, int x, int y)
{
    if (!IsValidePosition(w, x, y) ) {
        return -1;
    }

    return w->slot[y * w->width + x].hit;
}


static int CheckWorld(World * w, int x, int y)
{
    if (!IsValidePosition(w, x, y) ) {
        return -1;
    }

    Object * obj = GetObjectAt(w, x, y);
    if (obj == 0) {
        return 0;
    }

    ToObject(&x, &y, obj);
    return GetObjectDefAt(x, y);
}

int AttackWorld(World * w, int x, int y)
{
    if (!IsValidePosition(w, x, y) ) {
        return -1;
    }

    if (w->slot[y*w->width + x].hit == 1) {
        return -1;
    }

    w->slot[y*w->width + x].hit = 1;

    Object * obj = GetObjectAt(w, x, y);
    if (obj == 0) {
        return 0;
    }

    ToObject(&x, &y, obj);
    return GetObjectDefAt(x, y);
}

static void RemoveObject(World * w, Object * obj)
{
    int x, y;
    for (y = obj_min_y; y <= obj_max_y; y++) {
        for(x = obj_min_x; x <= obj_max_x; x++) {
            int c = GetObjectDefAt(x, y);
            if (c > 0) {
                int world_x = x, world_y = y;
                ToWorld(&world_x, &world_y, obj);
                SetObject(w, world_x, world_y, 0);
            }
        }
    }

    if (obj == w->obj_list) {
        w->obj_list = obj->next;
    } else {
        Object * ite = w->obj_list;
        while(ite->next) {
            if (ite->next == obj) {
                ite->next = obj->next;
                break;
            }
            ite = ite->next;
        }

    }
    free(obj);
}

int RemoveObjectAt(World * w, int x, int y)
{
    if (!IsValidePosition(w, x, y)) {
        return -1;
    }

    Object * obj = w->slot[y * w->width + x].obj;
    if (obj == 0) {
        return -1;
    }

    RemoveObject(w, obj);
    return 0;
}

char * DumpWorld(World * w, char * input)
{
    if (input == 0) {
        input = malloc(sizeof(char*) * w->width * w->height + 1);
    }
    if (input == 0) {
        return 0;
    }

    char * ptr = input;
    int x,y;
    for (y = 0; y < w->height; y++) {
        for (x = 0; x < w->width; x++) {
            int ret = CheckWorld(w, x, y);
            *ptr = '0' + ret;
            ptr++;
        }
    }
    *ptr = 0;
    return input;
}

static void DrawWorld(World * w)
{
    int x, y;
    for (y = w->height - 1; y >= 0; y--) {
    //    printf("%x  ", y);
        for (x = 0; x < w->width; x++) {
            Object * o = GetObjectAt(w, x, y);
            if (o == 0) {
                if (IsHit(w, x,y) ) {
                    printf("0  ");
                } else {
                    printf(".  ");
                }
            } else {
                int obj_x = x, obj_y = y;
                ToObject(&obj_x, &obj_y, o);
                int c = GetObjectDefAt(obj_x, obj_y);
                assert(c > 0);
                if (c > 0) {
                    if (IsHit(w, x,y)) {
                        if (c == 2) {
                            printf("x  ");
                        } else {
                            printf("+  ");
                        }
                    } else {
                        printf("%d  ", c);
                    }
                }
            }
        }
        printf("\n");
    }
    /*
    printf("\n   ");
    for (x = 0; x < w->width; x++) {
        printf("%x  ", x);
    }
    */
    printf("\n");
}

#ifdef _TEST
int main(int argc, char * argv[])
{
    int x = 10;
    int y = 10;
    if (argc == 3) {
        x = atoi(argv[1]);
        y = atoi(argv[2]);
    }

    World * w = CreateWorld(x, y, 3);
    DrawWorld(w);
    char buff[1024];
    printf("%s\n", DumpWorld(w, buff));
    return 0;
}
#endif
